Java NIO - 序列化与反序列化 - 使用JSON协议通信

JSON(JavaScript Object Notation,JS 对象简谱)是一种轻量级的数据交换格式。它是基于 ECMAScript(欧洲计算机协会制定的 JS 规范)的一个子集,采用完全独立于编程语言的文本格式来存储和表示数据。简洁和清晰的层次结构使得 JSON 成为理想的数据交换语言。JSON 协议是一种文本协议,非常易于人阅读和编写,同时也易于机器解析和生成,并能有效地提升网络传输效率。

JSON 的序列化和反序列化实践

在实际开发中,目前主流的策略是Gson和FastJson结合使用。在 POJO序列化成JSON字符串的应用场景下,使用谷歌的Gson库;在 JSON 字符串反序列化成POJO的应用场景下,使用阿里巴巴的FastJson库。

1
2
3
4
5
6
7
8
9
10
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>2.0.60</version>
</dependency>

工具类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
import com.alibaba.fastjson.JSONObject;
import com.google.gson.GsonBuilder;

public class JsonUtil {
// 谷歌GsonBuilder构造器
static GsonBuilder gb = new GsonBuilder();

static {
//不需要html escape
gb.disableHtmlEscaping();
}

// 序列化:使用Gson将 POJO 转成字符串
public static String pojoToJson(java.lang.Object obj) {
String json = gb.create().toJson(obj);
return json;
}

// 反序列化:使用Fastjson将字符串转成 POJO对象
public static <T> T jsonToPojo(String json, Class<T> tClass) {
T t = JSONObject.parseObject(json, tClass);
return t;
}
}

业务代码测试:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Data
public class JsonMsg {
private int id; // id Field(字段)
private String content;// content Field(字段)

public JsonMsg() {}
public JsonMsg(int id, String content) {
this.id = id;
this.content = content;
}

// 序列化:调用通用方法,使用Gson转成字符串
public String convertToJson() {
return JsonUtil.pojoToJson(this);
}

// 反序列化:使用FastJson转成Java POJO对象
public static JsonMsg parseFromJson(String json) {
return JsonUtil.jsonToPojo(json, JsonMsg.class);
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class JsonMsgDemo {
// 构建JSON对象
public JsonMsg buildMsg() {
JsonMsg user = new JsonMsg();
user.setId(1000);
user.setContent("密涅瓦的猫头鹰在黄昏起飞。");
return user;
}

// 测试用例:serialization & Deserialization
@Test
public void serAndDesr() throws IOException {
JsonMsg message = buildMsg();
// 将POJO对象序列化成字符串
String json = message.convertToJson();
// 可以用于网络传输,保存到内存或外存
System.out.println("json:=" + json);

// 将JSON 字符串反序列化成POJO对象
JsonMsg inMsg = JsonMsg.parseFromJson(json);
System.out.println("id:=" + inMsg.getId());
System.out.println("content:=" + inMsg.getContent());
}
}


JSON传输的服务端的实战案例

为了清晰地演示JSON传输,下面设计一个简单的客户端/服务端传 输程序:客户端将POJO转换成JSON字符串,编码后发送到服务端。服务器接收客户端的数据包,并解码成JSON,再转换成 POJO。为了简化流程,此服务端的代码仅仅包含 Inbound 入站处理的流 程,不包含 OutBound 出站处理的流程,是一个“丢弃”服务器。也就是说,服务端的程序仅仅读取客户端数据包并完成解码,服务端的程 序没有写出任何输出数据包到对端(客户端)。服务端实战案例的程序代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.util.CharsetUtil;
import utils.JsonMsg;

public class JsonServer {
private final int port;

public JsonServer(int port) {
this.port = port;
}

public void runServer() {
// 1. 创建反应器线程组
EventLoopGroup bossLoopGroup = new MultiThreadIoEventLoopGroup(1, NioIoHandler.newFactory());
EventLoopGroup workerLoopGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
try {
// 2. 服务端引导类
ServerBootstrap b = new ServerBootstrap();
b.group(bossLoopGroup, workerLoopGroup)
.channel(NioServerSocketChannel.class) // 设置 NIO 通道类型
.option(ChannelOption.SO_BACKLOG, 128)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
// 3. 装配子通道流水线
@Override
protected void initChannel(SocketChannel ch) {
// 处理器 1:长度域解码器(解决半包粘包问题)
// maxFrameLength: 1024, lengthFieldOffset: 0, lengthFieldLength: 4,
// lengthAdjustment: 0, initialBytesToStrip: 4 (跳过长度字段读取内容)
ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 0, 4, 0, 4));

// 处理器 2:String 解码器(ByteBuf -> String)
ch.pipeline().addLast(new StringDecoder(CharsetUtil.UTF_8));

// 处理器 3:业务逻辑解码器(String -> POJO)
ch.pipeline().addLast(new JsonMsgDecoder());
}
});

// 4. 绑定端口,开始监听
ChannelFuture f = b.bind(port).sync();
System.out.println("JSON 服务器启动成功,端口:" + port);

// 5. 等待通道关闭
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
System.out.println("服务器运行异常:" + e.getMessage());
} finally {
// 6. 优雅关闭
bossLoopGroup.shutdownGracefully();
workerLoopGroup.shutdownGracefully();
}
}

/**
* 业务处理器:负责将 String 转换为 POJO
*/
static class JsonMsgDecoder extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
// 经过前面的 StringDecoder,msg 已经是字符串类型
String json = (String) msg;

// 将 JSON 转换为 POJO 对象
JsonMsg jsonMsg = JsonMsg.parseFromJson(json);
System.out.println("收到一个 Json 数据包 =>> " + jsonMsg);

// 由于是“丢弃服务器”,处理完后不需要继续传递或写回
// 但在生产环境中,通常会释放资源或进行下一步业务处理
}
}

public static void main(String[] args) {
new JsonServer(9000).runServer();
}
}

为了简化流程,客户端的代码仅仅包含Outbound出站处理的流 程,不包含Inbound入站处理的流程。也就是说,客户端的程序仅仅进 行数据的编码,然后把数据包写到服务端。客户端的程序并没有去处 理从对端(服务端)过来的输入数据包。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioIoHandler;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldPrepender;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import utils.JsonMsg;

public class JsonSendClient {
private final String serverIp;
private final int serverPort;

public JsonSendClient(String ip, int port) {
this.serverIp = ip;
this.serverPort = port;
}

public void runClient() {
// 1. 创建反应器线程组
EventLoopGroup workerLoopGroup = new MultiThreadIoEventLoopGroup(NioIoHandler.newFactory());
try {
// 2. 客户端引导类
Bootstrap b = new Bootstrap();
b.group(workerLoopGroup)
.channel(NioSocketChannel.class) // 设置 NIO 通道类型
.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
.handler(new ChannelInitializer<SocketChannel>() {
// 3. 装配通道流水线
@Override
protected void initChannel(SocketChannel ch) {
// 出站处理器 1:自动添加长度字段头
// 在消息体前增加 4 字节的长度字段
ch.pipeline().addLast(new LengthFieldPrepender(4));

// 出站处理器 2:String 编码器 (String -> ByteBuf)
ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
}
});

// 4. 连接服务器
ChannelFuture f = b.connect(serverIp, serverPort).sync();
System.out.println("已连接至服务端:" + serverIp + ":" + serverPort);
Channel channel = f.channel();

// 5. 循环发送 JSON 字符串对象
String content = "密涅瓦的猫头鹰在黄昏起飞。";
for (int i = 0; i < 1000; i++) {
JsonMsg message = new JsonMsg(i, i + " -> " + content);
String json = message.convertToJson();

// 发送数据
// writeAndFlush 会经过 StringEncoder 转为 ByteBuf,再经过 LengthFieldPrepender 加头
channel.writeAndFlush(json); // 出站流水线是从后往前执行
System.out.println("发送第 " + i + " 个报文:" + json);
}

// 6. 等待连接关闭
channel.closeFuture().sync();
} catch (Exception e) {
System.out.println("客户端运行异常: " + e.getMessage());
} finally {
// 7. 优雅关闭
workerLoopGroup.shutdownGracefully();
}
}

public static void main(String[] args) {
// 连接本地 9000 端口
new JsonSendClient("127.0.0.1", 9000).runClient();
}
}